require 'socket'
require 'drb'

module MultiSketchup
  class CamBroker
    attr_accessor :host, :port, :camera, :config, :current_role_index
    attr_reader :delegated_role_names

    def self.locate_broker(config)
      DRbObject.new(nil, "druby://#{config['host']}:#{config['port']}")
    end    
    
    def initialize(config)
      @config = config
      @camera = nil
      @delegated_role_names = []
      @current_role_index = 0
    end
    
    def pop_next_available_role
    
      # HACK: always server
      #puts "# HACK: always server"
      #@current_role_index = 0 
      # END HACK
      
      next_role_name = @config.roles[@current_role_index].keys.first
      @current_role_index  = (@current_role_index + 1) % (@config.roles.size)
      
      puts "Delegating #{next_role_name}"
      
      return next_role_name
      
      #@current_role_index = @config.roles
      #next_available_role_name = @config.roles.map do |role|
      #  role.keys.first
      #end.detect do |role_name|
      #  !@delegated_role_names.include?(role_name)
      #end
      
      #@delegated_role_names << next_available_role_name
      
      #log "Delegated role: #{next_available_role_name}"
      
      #return next_available_role_name
    end
    
    def release_role(role_name)
      log "Disconnected: #{role_name}"
      # @delegated_role_names.delete(role_name)
    end
    
    def has_delegated_server?
      !@config['roles'].keys.include?('server')
    end
    
    def super_warp_out
      `ruby -rnet/http -e 'Net::HTTP::get(URI.parse("#{@config.warp.uri}"))'`
    end
    
    def run
      log "Starting on druby://#{@config['host']}:#{@config['port']}"
      DRb.start_service("druby://#{@config['host']}:#{@config['port']}", self)
    end

  end
  
  class CamServer
    attr_accessor :broker, :config, :rr_param_cache
    
    def initialize(config)
      @config = config
      role = @config.roles.detect{|role|role.keys.first == 'server'}.values.first
      
      log "Starting server..."
      @broker = DRbObject.new(nil, "druby://#{config.host}:#{config.port}")
      Sketchup.active_model.active_view.camera.fov = role.fov
      
      # RoboRealm velocity and last polling time
      @rr_param_cache = [0,0,0]
      @last_rr_poll_time = Time.now
    end
    
    def run
      if @config.robo_realm.enabled
        open_rr_sock
        Sketchup.active_model.active_view.animation = self         # Intercept nextFrame to read RoboRealm
      else
        @rr_sock = nil
        Sketchup.active_model.active_view.add_observer( self )     # Intercept onViewChanged
      end
      
      init_camera
      
      # Intercept onQuit
      Sketchup.add_observer(self)
    end
    
    def init_camera
    
      log "init_camera"
      view = Sketchup.active_model.active_view

      eye    = Geom::Point3d.new(4247.49, -11.3292, 68.6031)
      target = Geom::Point3d.new(3285.25, -38.9495, 150.526)
      up     = Geom::Vector3d.new(0,0,1.0)
      
      view.camera.set(eye,target,up)
    end

    attr_accessor :rr_sock
        
    def open_rr_sock      
      UDPSocket.do_not_reverse_lookup = true
      
      # Bind on 7071 to recv response
      @rr_sock = UDPSocket.new
      @rr_sock.bind("", 7071)
    end

    def close_rr_sock  
      @rr_sock.close
    end
  
    def read_robo_realm_params
      
      @time_elapsed_since_last_rr_poll = Time.now - @last_rr_poll_time
      
      if ( @time_elapsed_since_last_rr_poll >= @config.robo_realm.sleep )
        @last_rr_poll_time = Time.now

        log "read robo_realm LIVE"
  
        @rr_sock.send("anything",0,@config.robo_realm.server_address,7070)
          
        # Receive response on 7071
        msg, sender = @rr_sock.recvfrom(255)
      
        @rr_param_cache = msg.split(',').collect{|s|s.to_f}
      else
        log "read robo_realm from cache"
      end
      
      return @rr_param_cache
    end
    
    def update_from_robo_realm(view)
      rrx, rry, rrbox = read_robo_realm_params
      
      
      if true # rrbox <= @config.robo_realm.max_box

        xmin = @config.robo_realm.xmin # 100
        xmax = @config.robo_realm.xmax # 500
        ymin = @config.robo_realm.ymin # 0
        ymax = @config.robo_realm.ymax # 300
        
        xmid = ( xmax + xmin ) / 2
        ymid = ( ymax + ymin ) / 2
        
        xhrange = ( xmax - xmin ) / 2 # half range
        yhrange = ( ymax - ymin ) / 2
        
        x = ( rrx - xmid ) / ( xhrange )
        y = ( rry - ymid ) / ( yhrange )
        
        log "#{rrx},#{rry}" #  normalizing to #{x},#{y}"
        
              
        # Normalize to [-1.0,1.0]
        # x = (rrx - 330.0)/200.0
        # y = (rry - 250.0)/150.0
        
        
        # Move camera according to RR input
        eye    = view.camera.eye
        target = view.camera.target
        up     = view.camera.up
        
        eye.x    += @config.robo_realm.speed.to_f * x  * @config.robo_realm.xfac
        eye.y    += @config.robo_realm.speed.to_f * y  * @config.robo_realm.yfac 
        target.x += @config.robo_realm.speed.to_f * x  * @config.robo_realm.xfac 
        target.y += @config.robo_realm.speed.to_f * y  * @config.robo_realm.yfac 
        
        view.camera.set(eye,target,up)

      end
    end
    
    def pause;log"pause";end
    def resume;log"resume";end
    def stop;log"stop";resume;end
        
    # If using RoboRealm input, recv new velocity vector,
    # update cam position, send updated cam to clients, check for warp out
    def nextFrame(view)
      update_from_robo_realm(view)

      # Send cam pos to clients
      @broker.camera = cam2s(view.camera)
      
      # Check for warp out
      warp_out if inside_warp_box?(view.camera.eye)
      
      return true # keep going
    end
    
    # If not using RoboRealm input, send updated cam to clients,
    # & check for warp-out
    def onViewChanged(view)
      # Send cam pos to clients
      @broker.camera = cam2s(view.camera)
      
      # Check for warp out
      warp_out if inside_warp_box?(view.camera.eye)
    end
    
    def onQuit
      @broker.release_role('server')
      
      close_rr_sock if @rr_response
    end
    
    private
    def warp_out
      log "Issuing HTTP GET with FREAKIN MAGICK..."
      # @broker.super_warp_out
       UI.openURL(@config.warp.uri)
      log "Sent HTTP GET."
    end

    
    def inside_warp_box?(pos)
      pos.x.to_f >= @config.warp.xmin &&
      pos.x.to_f <= @config.warp.xmax &&
      pos.y.to_f >= @config.warp.ymin &&
      pos.y.to_f <= @config.warp.ymax
    end

    def cam2s(cam)
      "#{cam.eye.x.to_f},#{cam.eye.y.to_f},#{cam.eye.z.to_f};" +
      "#{cam.target.x.to_f},#{cam.target.y.to_f},#{cam.target.z.to_f};" +
      "#{cam.up.x.to_f},#{cam.up.y.to_f},#{cam.up.z.to_f}"
    end
  end
  
  class CamClient
    attr_accessor :server, :role_name, :rotation, :fov
    
    def initialize(config,role_name)
      
      @broker = DRbObject.new(nil, "druby://#{config.host}:#{config.port}")
       
      @role_name = role_name
      role = config.roles.detect{|role|log role.keys.first;role.keys.first == role_name}.values.first
      @fov = role['fov']
      @rotation = role['rotation']
      
      log "Starting #{role_name}..."
       
      Sketchup.active_model.active_view.camera.fov = @fov
    end
    
    def run
      # Intercept nextFrame
      Sketchup.active_model.active_view.animation = self
      
      # Intercept onQuit
      Sketchup.add_observer(self)
    end

    def onQuit
      @broker.release_role(@role_name)
    end
    
    def nextFrame(view)
      
      eye,target,up = s2cam(@broker.camera)
      
      real_up = Geom::Vector3d.new(0,0,1)
      
		  rot_xform = Geom::Transformation.rotation(eye,real_up, deg2rad(@rotation))
      target_vec = Geom::Vector3d.new(target.x-eye.x,target.y-eye.y,target.z-eye.z)
      new_target_vec = rot_xform * target_vec
      new_target = Geom::Point3d.new( new_target_vec.x+eye.x,new_target_vec.y+eye.y,new_target_vec.z+eye.z )
      
      view.camera.set( eye, new_target, real_up )
    end
    
    private
    def deg2rad(d)
      Math::PI * d / 180.0
    end

    def s2cam(s)
      eye    =  Geom::Point3d.new(s.split(';')[0].split(',').map{|k|k.to_f}),
    	target =  Geom::Point3d.new(s.split(';')[1].split(',').map{|k|k.to_f}),
  	  up     = Geom::Vector3d.new(s.split(';')[2].split(',').map{|k|k.to_f})
    end
  end
    
end

